package cn.yangliu.jpa.handler;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import cn.yangliu.jpa.annotations.qbc.JpaQbcQuery;
import cn.yangliu.jpa.enums.LinkTypeEnum;
import cn.yangliu.jpa.enums.QueryTypeEnum;
import cn.yangliu.jpa.function.PredicateBuilder;
import cn.yangliu.jpa.tools.JpaUtils;
import org.springframework.data.jpa.domain.Specification;

/**
 * The interface Param transformer.
 *
 * @author 问道于盲
 * @date 2019-06-14
 */
class SpecificationHandler {

    /**
     * Instantiates a new Specification handler.
     * forbid other class to create a new instance.
     * only itself can create a new instance.
     */
    private SpecificationHandler() {
	}

    /**
     * 构建Specification 查询条件
     *
     * @param <T>       实体类型
     * @param queryPojo 查询封装
     * @return Specification specification
     */
    static <T> Specification<T> buildSpecification(Object queryPojo) {
		return buildSpecification(queryPojo, null);
	}

    /**
     * 构建Specification 查询条件
     *
     * @param <T>       实体类型
     * @param queryPojo 查询封装
     * @param builder   builder
     * @return Specification specification
     */
    static <T> Specification<T> buildSpecification(Object queryPojo, PredicateBuilder builder) {
		List<Field> fieldList = JpaUtils.getDeclaredFields(queryPojo);
		fieldList = JpaUtils.sort(fieldList, JpaQbcQuery.class);
		List<Specification<T>> specifications = new ArrayList<>();

		for (Field field : fieldList) {

			Object value = JpaUtils.getFieldValue(queryPojo, field);
			if (Objects.isNull(value) || Objects.equals(value, "")) {
				continue;
			}

			JpaQbcQuery jpaQbcQuery = field.getDeclaredAnnotation(JpaQbcQuery.class);

			if (Objects.isNull(jpaQbcQuery)) {
				continue;
			}

			String[] fieldNames = jpaQbcQuery.fields();
			Specification<T> objectSpecification = null;

			if (fieldNames.length == 0) {
				String fieldName = field.getName();;
				objectSpecification = buildSpecification4Field(jpaQbcQuery, fieldName, value, builder);
			}else if (fieldNames.length == 1) {
				String fieldName = fieldNames[0];
				objectSpecification = buildSpecification4Field(jpaQbcQuery, fieldName, value, builder);
			}else {
			    if (value.getClass().isArray() || value instanceof Collection ) {
			        throw new UnsupportedOperationException("QBC is not support 'in query' more than 1 field, suggest use jpql query");
                }
				objectSpecification = buildInnerSpecification(builder, value, jpaQbcQuery, fieldNames);
			}

			specifications.add(objectSpecification);

		}

		Specification<T> temp = null;
		for (int i = (specifications.size()-1); i >= 0; i-- ) {
			Specification<T> specification = specifications.get(i);
			if (Objects.isNull(temp)){
				temp = specification;
				continue;
			}
            temp = temp.and(specification);
		}

		return temp;
	}

    /**
     * 构建每个属性的查询的Specification
     *
     * @param <T>         the type parameter
     * @param jpaQbcQuery the jpa qbc query
     * @param fieldName   the field name
     * @param value       the value
     * @param pb          the pb
     * @return the specification
     */
    private static  <T> Specification<T> buildSpecification4Field(JpaQbcQuery jpaQbcQuery, String fieldName, Object value,PredicateBuilder pb){
		QueryTypeEnum queryTypeEnum = jpaQbcQuery.type();

		return (root,  query, builder)->{

			Predicate predicate = null;
			if (Objects.nonNull(pb)) {
				predicate = pb.build(root, builder, fieldName, value);
			}

			if (Objects.isNull(predicate)) {
				predicate = buildPredicate(root, builder, value, fieldName, queryTypeEnum);
			}

			return predicate;
		};

	}


    /**
     * 构建相同属性值但多个查询属性的Specification
	 * 如 查询对象的name属性 对应多个实体对象的属性查询,将其作为一个组合
	 * 查询对象 name='zhang'
	 * 实体对象 username和nickname
	 * (username like 'zhang' or nickname like 'zhangfei')
     *
     * @param <T>         the type parameter
     * @param pb          the pb
     * @param value       the value
     * @param jpaQbcQuery the jpa qbc query
     * @param fieldNames  the field names
     * @return the specification
     */
    private static <T> Specification<T> buildInnerSpecification(PredicateBuilder pb, Object value,
															JpaQbcQuery jpaQbcQuery, String[] fieldNames) {
		LinkTypeEnum innerLink = jpaQbcQuery.innerLink();

		QueryTypeEnum queryTypeEnum = jpaQbcQuery.type();

		return (root,  query, builder)->{

			List<Predicate> predicates = new ArrayList<>();

			for (String fieldName : fieldNames) {
				Predicate predicate = null;
				if (Objects.nonNull(pb)) {
					predicate = pb.build(root, builder, fieldName, value);
				}

				if (Objects.isNull(predicate)) {
					predicate = buildPredicate(root, builder, value, fieldName, queryTypeEnum);
				}

				predicates.add(predicate);
			}

			if (Objects.equals(innerLink, LinkTypeEnum.OR)) {
				return builder.or(predicates.toArray(new Predicate[]{}));
			}

			return builder.and(predicates.toArray(new Predicate[]{}));
		};
	}


    /**
     * 构建Predicate
     *
     * @param <T>           泛型
     * @param root          Root
     * @param cb            CriteriaBuilder
     * @param value         属性值
     * @param fieldName     属性名
     * @param queryTypeEnum 查询方式
     * @return Predicate predicate
     */
    @SuppressWarnings("all")
	private static <T> Predicate buildPredicate(Root<T> root, CriteriaBuilder cb, Object value, String fieldName,
			QueryTypeEnum queryTypeEnum) {
		Predicate predicate = null;

		if (Objects.equals(QueryTypeEnum.LIKE, queryTypeEnum)) {
			Path<String> path = root.get(fieldName);
			predicate = cb.like(path, "%" + value.toString() + "%");
		} else if (Objects.equals(QueryTypeEnum.STARTWITH, queryTypeEnum)) {
			Path<String> path = root.get(fieldName);
			predicate = cb.like(path, value.toString() + "%");
		} else if (Objects.equals(QueryTypeEnum.ENDWITH, queryTypeEnum)) {
			Path<String> path = root.get(fieldName);
			predicate = cb.like(path, "%" + value.toString());
		} else if (Objects.equals(QueryTypeEnum.EQ, queryTypeEnum)) {
			Path<?> path = root.get(fieldName);
			predicate = cb.equal(path, value);
		} else if (Objects.equals(QueryTypeEnum.LT, queryTypeEnum)) {
			Path<Comparable> path = root.get(fieldName);
			predicate = cb.lessThan(path, (Comparable) value);
		} else if (Objects.equals(QueryTypeEnum.GT, queryTypeEnum)) {
			Path<Comparable> path = root.get(fieldName);
			predicate = cb.greaterThan(path, (Comparable) value);
		} else if (Objects.equals(QueryTypeEnum.LTE, queryTypeEnum)) {
			Path<Comparable> path = root.get(fieldName);
			predicate = cb.lessThanOrEqualTo(path, (Comparable) value);
		} else if (Objects.equals(QueryTypeEnum.GTE, queryTypeEnum)) {
			Path<Comparable> path = root.get(fieldName);
			predicate = cb.greaterThanOrEqualTo(path, (Comparable) value);
		} else if (Objects.equals(QueryTypeEnum.NOTEQ, queryTypeEnum)) {
			Path<?> path = root.get(fieldName);
			predicate = cb.notEqual(path, value);
		} else if (Objects.equals(QueryTypeEnum.IN, queryTypeEnum)) {
			Expression<?> exp = root.get(fieldName);
			if (value instanceof Collection) {
				predicate = exp.in((Collection)value);
			}
			if (value.getClass().isArray()) {
				predicate = exp.in((Object[])value);
			}

		}
		return predicate;
	}

}
